library("tidyverse")
library("ggplot2")
library(plyr)
library("mco")
library(MLmetrics)
library(KraljicMatrix)
run_1 = read_csv('/Users/b1017579/Documents/PhD/Projects/10-ELECSIM/run/validation-optimisation/data/run_2.csv')
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
X1 = col_double(),
id = col_double(),
run_number = col_double(),
time_taken = col_double(),
timestamp_start = col_double(),
timestamp_end = col_double(),
reward = col_double(),
individual_m = col_double(),
individual_c = col_double(),
coal = col_double(),
nuclear = col_double(),
ccgt = col_double(),
wind = col_double(),
solar = col_double()
)
tail(run_1)
ggplot(filter(run_1), aes(y=individual_m, x=individual_c)) + geom_hex(bins=10)

p = ggplot(run_1, aes(y=individual_m, x=individual_c, color=reward), size=10) + facet_wrap(~run_number) + geom_point() + ggtitle("Scatter plot of input parameters and reward")
p$labels$fill <- "Absolute \nPercentage \nError"
print(p)

p = ggplot(run_1, aes(y=individual_m, x=individual_c, color=reward), size=10) + facet_wrap(~run_number) + geom_jitter() + ggtitle("Scatter (jitter) plot of input parameters and reward")
p$labels$fill <- "Absolute \nPercentage \nError"
print(p)

p = ggplot(run_1, aes(y=individual_m, x=individual_c, color=time_taken), size=10) + facet_wrap(~run_number) + geom_jitter() + ggtitle("Scatter plot of input parameters and time taken")
p$labels$fill <- "Time \nTaken"
print(p)

p=ggplot(run_1, aes(y=individual_m, x=individual_c)) + stat_summary_hex(aes(z = reward), bins=10) + facet_wrap(~ run_number) + scale_x_continuous(breaks = round(seq(min(run_1$individual_c), max(run_1$individual_c), by = 25),1)) + ggtitle("Hexagonal heatmap of input parameters and Absolute Percentage Error")
p$labels$fill <- "Absolute \nPercentage \nError"
print(p)
ggsave("~/Desktop/genetic_algorithm_progression.png")
Saving 6.89 x 4.26 in image

run_1 %>% dplyr::group_by(run_number) %>% dplyr::summarise(avg_reward = mean(reward)) %>% ggplot()+ geom_smooth(data=run_1, aes(x=run_number, reward))+geom_line(aes(x=run_number, y=avg_reward))+ggtitle("Average reward vs Population Number")

# accurate_area = filter(run_1, individual_c<-4.8, individual_c>-50, individual_m <0.003, individual_m > 0.0023)
# accurate_area = filter(run_1, reward < 0.2)
# accurate_area = filter(run_1, run_number==11)
accurate_area = filter(run_1, run_number == 16)
accurate_area
ggplot(data=accurate_area, aes(x=individual_c, y=individual_m, color=reward))+geom_jitter()+geom_point(color="red", alpha=0.5) + ggtitle("Scatter and Jitterplot of Reward against Input Parameters for \nLast Population of GA")

accurate_area_long = gather(accurate_area, "key", "value", "coal", "ccgt", "wind", "nuclear", "solar")
accurate_area_long_perc = accurate_area_long %>% group_by(id) %>% mutate(value_perc = value/sum(value))
params_more_than_10 = run_1 %>% group_by(individual_c, individual_m) %>% dplyr::mutate(number = n()) %>% ungroup() %>% filter(number> 10) %>% dplyr::mutate_if(is.numeric, round, 6)
params_more_than_10 %>% ggplot(aes(as.factor(individual_c), reward)) +geom_violin()+facet_wrap(~individual_m)+ theme(axis.text.x = element_text(angle = 90, hjust = 1)) +geom_jitter(position=position_jitter(0.2), alpha=0.05) + ggtitle("Violin plot of input parameters with more than 10 simulations \nagainst distribution of reward")

accurate_area %>% mutate_if(is.numeric, round, 6) %>% ggplot(aes(as.factor(individual_c), reward)) +geom_violin()+facet_wrap(~individual_m)+ theme(axis.text.x = element_text(angle = 90, hjust = 1)) +geom_jitter(position=position_jitter(0.2))+ ggtitle("Violin plot of input parameters with more than 10 simulations of last population of GA\nagainst distribution of reward")

actual_mix = read_csv('/Users/b1017579/Documents/PhD/Projects/10-ELECSIM/elecsim/data/processed/electricity_mix/energy_mix_historical.csv')
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
X1 = col_double(),
year = col_double(),
variable = col_character(),
value = col_double()
)
actual_mix_2018 = filter(actual_mix, year==2018)
actual_mix_2018$type = "actual"
actual_mix_2018
actual_mix_2018_reduced = filter(actual_mix_2018, variable %in% c("ccgt", 'wind', 'nuclear', 'solar', 'coal'))
actual_mix_2018_reduced = actual_mix_2018_reduced %>% mutate(value_perc = value/sum(value))
actual_mix_2018_reduced = dplyr::rename(actual_mix_2018_reduced, key=variable)
head(actual_mix_2018_reduced)
accurate_area_long_perc$type = 'predicted'
accurate_area_long_perc
comparison = rbind(select(ungroup(accurate_area_long_perc), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
ggplot(comparison, aes(x=key, y=value_perc, fill=type)) +
stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ylab("Energy Mix (%)") + ggtitle("Combination of all runs in last genetic algorithm run")
ggsave('~/Desktop/average_error_of_best_params.png')
Saving 6.89 x 4.26 in image

best_result = run_1 %>% group_by("id") %>% filter(reward==min(reward)) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type="predicted")
comparison_best = rbind(select(ungroup(best_result), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
ggplot(comparison_best, aes(x=key, y=value_perc, fill=type)) + geom_col(position = "dodge2") + ylab("Energy Mix (%)") + ggtitle("Best single run")

pdc_example = read_csv('/Users/b1017579/Documents/PhD/Projects/10-ELECSIM/notebooks/validation-optimisation/data/price_demand_curve_example.csv')
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
X1 = col_double(),
accepted_price = col_double(),
segment_demand = col_double(),
segment_hour = col_double()
)
demand_range = data.frame(x=seq(from=min(pdc_example$segment_demand),max(pdc_example$segment_demand),length.out=500))
get_line = function(c, m){
y = m * demand_range + c
return(y)
}
get_x = function(){
return(demand_range)
}
# lines = accurate_area %>% group_by(id) %>% apply(get_line(.))
lines = ddply(accurate_area, .(id), transform, y=get_line(individual_c, individual_m), x=get_x())
ggplot() + geom_line(data=lines, aes(x=x.1, y=x, group=id, color=reward)) + stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="red") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Last genetic algorithm run price curves between 2023-2028 \ncompared to price in 2018")

best_75_percentile = accurate_area %>% group_by(individual_c, individual_m) %>% dplyr::summarise(number=n(), quantiles = list(enframe(quantile(reward, probs=c(0.25,0.5,0.75))))) %>% unnest %>% ungroup() %>% filter(number>10) %>% group_by(name) %>%filter(rank(value, ties.method="first")==1)
p = ddply(best_75_percentile, .(name), transform, y=get_line(individual_c, individual_m), x=get_x()) %>% ggplot() + geom_line(aes(x=x.1, y=x, color=name))+ stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="yellow") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Price curve of best runs at different percentiles of last genetic \nalgorithm population")
p$labels$fill <- "Percentiles"
print(p)

best_percentiles_comparison = inner_join(accurate_area, best_75_percentile, by=c('individual_c', 'individual_m')) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type=name)
comparison_percentiles = rbind(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
comparison_percentiles %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Comparison of energy mix of final genetic algorithm population \nat different quantile levels")

rbind(filter(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'),type=="50%"), select(actual_mix_2018_reduced, -X1, -year)) %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Best parameter combinations from final genetic algorithm run")

BEST PARAMETER COMBINATION FROM ALL RUNS
best_75_percentile_all_runs = run_1 %>% group_by(individual_c, individual_m) %>% dplyr::summarise(number=n(), quantiles = list(enframe(quantile(reward, probs=c(0.25,0.5,0.75, 0.9, 1.0))))) %>% unnest %>% ungroup() %>% filter(number>10) %>% group_by(name) %>%filter(rank(value, ties.method="first")==1)
p = ddply(best_75_percentile_all_runs, .(name), transform, y=get_line(individual_c, individual_m), x=get_x()) %>% ggplot() + geom_line(aes(x=x.1, y=x, color=name))+ stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="yellow") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Percentiles from all runs")
p$labels$fill <- "Percentiles"
print(p)

best_percentiles_comparison = inner_join(run_1, best_75_percentile_all_runs, by=c('individual_c', 'individual_m')) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type=name)
comparison_percentiles = rbind(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
comparison_percentiles %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2")

rbind(filter(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'),type=="75%" | type=="90%"), select(actual_mix_2018_reduced, -X1, -year)) %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Best parameter combinations from all genetic algorithm runs")

Calculation of best quantile to select combination of parameters for lowest reward
best_params = params_more_than_10 %>% group_by(individual_c, individual_m) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% group_by(id, key) %>% inner_join(actual_mix_2018_reduced, by="key") %>% mutate(diff_perc = abs(value_perc.x-value_perc.y)) %>% group_by(id) %>% summarise(mean_diff_perc = mean(diff_perc), individual_c = mean(individual_c), individual_m = mean(individual_m)) %>% filter(rank(mean_diff_perc, ties.method="first")==1)
best_params_comparison = filter(run_1,dplyr::near(x=individual_c, y=best_params$individual_c[1], tol=0.00001),dplyr::near(x=individual_m, y=best_params$individual_m[1], tol=0.00001)) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% dplyr::group_by(id) %>% dplyr::mutate(value_perc = value/sum(value)) %>% dplyr::mutate(type="predicted")
comparison_best_params = rbind(select(ungroup(best_params_comparison), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
comparison_best_params %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Comparison of predicted vs actual for best parameter set over 10 from all runs")

## Getting MAPE for best param
predicted_means_best_param = dplyr::filter(run_1,dplyr::near(x=individual_c, y=best_params$individual_c[1], tol=0.1),dplyr::near(x=individual_m, y=best_params$individual_m[1], tol=0.1)) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% dplyr::group_by(id) %>% dplyr::mutate(value_perc = value/sum(value)) %>% dplyr::mutate(type="predicted") %>% dplyr::group_by(individual_c, individual_m, key) %>% dplyr::summarise(predicted_perc = mean(value_perc))
print(paste("MAPE = ", MAPE(predicted_means_best_param$predicted_perc, actual_mix_2018_reduced$value_perc)))
[1] "MAPE = 1.75551710439338"
p = best_params %>% transform(y=get_line(individual_c, individual_m), x=get_x()) %>% ggplot() + geom_line(aes(x=x.1, y=x))+ stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="yellow") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Best paramater combination price curve")
p$labels$fill <- "Percentiles"
print(p)

Calculation of best parameters with lowest variance and error
# best_params = params_more_than_10 %>% group_by(individual_c, individual_m) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% group_by(id, key) %>% inner_join(actual_mix_2018_reduced, by="key") %>% mutate(diff_perc = abs(value_perc.x-value_perc.y)) %>% group_by(id) %>% summarise(mean_diff_perc = mean(diff_perc), individual_c = mean(individual_c), individual_m = mean(individual_m)) %>% filter(rank(mean_diff_perc, ties.method="first")==1)
# calculate_mean_error = function(df){
#
# return(gather(df, "key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% inner_join(actual_mix_2018_reduced, by='key'))
# }
# params_more_than_10 %>% group_by(individual_c, individual_m) %>% do(., calculate_mean_error)
# ddply(params_more_than_10, .(individual_c, individual_m), calculate_mean_error)
params_sd_mean_reward = params_more_than_10 %>% dplyr::group_by(individual_c, individual_m) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% dplyr::group_by(id) %>% dplyr::mutate(value_perc = value/sum(value)) %>% dplyr::group_by(id, key) %>% dplyr::inner_join(actual_mix_2018_reduced, by="key") %>% dplyr::mutate(diff_perc = abs(value_perc.x-value_perc.y)) %>% dplyr::group_by(id) %>% dplyr::summarise(diff_sd = sd(diff_perc), mean_diff_perc = mean(diff_perc), individual_c = mean(individual_c), individual_m = mean(individual_m)) %>% dplyr::group_by(individual_c, individual_m) %>% dplyr::summarise(diff_sd = sd(diff_sd), mean_diff_perc = dplyr::mean(mean_diff_perc))
params_sd_mean_reward_ordered = arrange(params_sd_mean_reward, diff_sd, mean_diff_perc)
frontier = get_frontier(as.data.frame(params_sd_mean_reward_ordered), diff_sd, mean_diff_perc, decreasing = FALSE, quadrant = "bottom.left")
params_sd_mean_reward %>% ggplot() + geom_point(aes(diff_sd, mean_diff_perc)) + geom_line(data=frontier, aes(diff_sd, mean_diff_perc), color="red") + ggtitle("Pareto frontier of standard deviation versus mean for simulations \nof more than 10 runs")

params_more_than_10_perc = params_more_than_10 %>% dplyr::group_by(individual_c, individual_m) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% dplyr::mutate(value_perc = value/sum(value))
frontier_full_data = frontier %>% dplyr::inner_join(params_sd_mean_reward_ordered, by=c('diff_sd', 'mean_diff_perc')) %>%dplyr:: inner_join(params_more_than_10_perc, by=c("individual_c", "individual_m")) %>% dplyr::mutate(type=paste(mean_diff_perc,diff_sd))
rbind(filter(select(frontier_full_data, "key", "type", "value", 'value_perc')), select(actual_mix_2018_reduced, -X1, -year)) %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Best parameter combinations from all genetic algorithm runs")

NA
best_one = dplyr::select(dplyr::filter(frontier_full_data, mean_diff_perc<0.067), "key", "type", "value", 'value_perc')
best_one$type = "Simulated"
actual_mix_2018_reduced$type = "Actual"
rbind(best_one, select(actual_mix_2018_reduced, -X1, -year)) %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + theme_classic() +theme(text = element_text(size=18))+ xlab("") + ylab("Electricity Mix (%)") +scale_fill_brewer(palette = 'Set2')
ggsave("~/Documents/PhD/Projects/10-ELECSIM/notebooks/validation-optimisation/figures/introduction/best_run.pdf")
Saving 6.89 x 4.26 in image

best_actual = rbind(best_one, select(actual_mix_2018_reduced, -X1, -year))
best_actual_stats = best_actual %>% dplyr::group_by(type, key) %>% dplyr::summarise(sd_value = sd(value_perc), mean_value = mean(value_perc))
actuals = dplyr::filter(best_actual_stats, type=="Actual")$mean_value
predicted = dplyr::filter(best_actual_stats, type=="Simulated")$mean_value
RMSE(actuals,predicted)
[1] 0.04512687

p=ggplot(run_1, aes(y=individual_m, x=individual_c)) + stat_summary_hex(aes(z = time_taken), bins=10) + facet_wrap(~ run_number) + scale_x_continuous(breaks = round(seq(min(run_1$individual_c), max(run_1$individual_c), by = 25),1)) + ggtitle("Hexagonal heatmap of input parameters against time taken with respect to population number")
p$labels$fill <- "Time \n Taken"
print(p)
ggsave('~/Desktop/time-taken-parameters.png')
Saving 6.89 x 4.26 in image

run_1 %>% arrange(desc(run_number)) %>% ggplot(alpha=0.1, aes(y=individual_m, x=individual_c)) + geom_point(aes(color=run_number, size=time_taken)) + ggtitle("Scatter plot showing run number against time taken and reward")

run_1 %>% filter(individual_c<-3, individual_m <0.0025, individual_m > 0.0015) %>% ggplot(aes(x=as.factor(run_number), y=time_taken)) + geom_violin() + ggtitle("Violin plot of time taken for each simulation run against population number")

ggplot(data=run_1, aes(x=as.factor(run_number), y=reward))+geom_boxplot()+geom_jitter(position=position_jitter(0.2), alpha=0.5, color='blue') + ggtitle("Boxplot and jitter of run number against reward")

run_1 %>% filter(individual_c<-3, individual_m <0.0025, individual_m > 0.0015) %>% ggplot(aes(x=as.factor(run_number), y=reward)) + geom_violin() + ggtitle("Violin plot of reward against population number")

ggplot(run_1, aes(x=reward, y=time_taken))+stat_smooth(method = "lm")+geom_point()+xlab("Absolute Percentage Error") + ggtitle("Scatter plot of time taken for each simulation run against \nabsolute percentage error")

ggplot()+geom_point(data=run_1, aes(x=time_taken, y=reward, color=run_number)) + ggtitle("Scatter plot of time taken against reward")

run_1
run_1_long = gather(run_1, "key", "value", "coal", "ccgt", "wind", "nuclear", "solar")
# run_1_long %>% inner_join(actual_mix_2018_reduced, by='key') %>% group_by(id,key) %>% mutate(total_difference = value.x-value.y) %>% group_by(id, key) %>% summarise(difference_sum = sum(total_difference))
ggplot(data=dif_sum, aes(x=tot_diff, y=time_taken, color=reward)) + geom_point()+geom_smooth()+xlim(-11000,10000)
Error in ggplot(data = dif_sum, aes(x = tot_diff, y = time_taken, color = reward)) :
object 'dif_sum' not found
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCmBgYHtyfQpsaWJyYXJ5KCJ0aWR5dmVyc2UiKQpsaWJyYXJ5KCJnZ3Bsb3QyIikKbGlicmFyeShwbHlyKQpsaWJyYXJ5KCJtY28iKQpsaWJyYXJ5KE1MbWV0cmljcykKbGlicmFyeShLcmFsamljTWF0cml4KQpgYGAKCmBgYHtyfQpydW5fMSA9IHJlYWRfY3N2KCcvVXNlcnMvYjEwMTc1NzkvRG9jdW1lbnRzL1BoRC9Qcm9qZWN0cy8xMC1FTEVDU0lNL3J1bi92YWxpZGF0aW9uLW9wdGltaXNhdGlvbi9kYXRhL3J1bl8yLmNzdicpCnRhaWwocnVuXzEpCmBgYApgYGB7cn0KZ2dwbG90KGZpbHRlcihydW5fMSksIGFlcyh5PWluZGl2aWR1YWxfbSwgeD1pbmRpdmlkdWFsX2MpKSArIGdlb21faGV4KGJpbnM9MTApCmBgYAoKYGBge3J9CnAgPSBnZ3Bsb3QocnVuXzEsIGFlcyh5PWluZGl2aWR1YWxfbSwgeD1pbmRpdmlkdWFsX2MsIGNvbG9yPXJld2FyZCksIHNpemU9MTApICsgZmFjZXRfd3JhcCh+cnVuX251bWJlcikgKyBnZW9tX3BvaW50KCkgKyBnZ3RpdGxlKCJTY2F0dGVyIHBsb3Qgb2YgaW5wdXQgcGFyYW1ldGVycyBhbmQgcmV3YXJkIikKcCRsYWJlbHMkZmlsbCA8LSAiQWJzb2x1dGUgXG5QZXJjZW50YWdlIFxuRXJyb3IiCnByaW50KHApCmBgYApgYGB7cn0KcCA9IGdncGxvdChydW5fMSwgYWVzKHk9aW5kaXZpZHVhbF9tLCB4PWluZGl2aWR1YWxfYywgY29sb3I9cmV3YXJkKSwgc2l6ZT0xMCkgKyBmYWNldF93cmFwKH5ydW5fbnVtYmVyKSArIGdlb21faml0dGVyKCkgKyBnZ3RpdGxlKCJTY2F0dGVyIChqaXR0ZXIpIHBsb3Qgb2YgaW5wdXQgcGFyYW1ldGVycyBhbmQgcmV3YXJkIikKcCRsYWJlbHMkZmlsbCA8LSAiQWJzb2x1dGUgXG5QZXJjZW50YWdlIFxuRXJyb3IiCnByaW50KHApCmBgYAoKCmBgYHtyfQpwID0gZ2dwbG90KHJ1bl8xLCBhZXMoeT1pbmRpdmlkdWFsX20sIHg9aW5kaXZpZHVhbF9jLCBjb2xvcj10aW1lX3Rha2VuKSwgc2l6ZT0xMCkgKyBmYWNldF93cmFwKH5ydW5fbnVtYmVyKSArIGdlb21faml0dGVyKCkgKyBnZ3RpdGxlKCJTY2F0dGVyIHBsb3Qgb2YgaW5wdXQgcGFyYW1ldGVycyBhbmQgdGltZSB0YWtlbiIpCnAkbGFiZWxzJGZpbGwgPC0gIlRpbWUgXG5UYWtlbiIKcHJpbnQocCkKCmBgYAoKYGBge3J9CnA9Z2dwbG90KHJ1bl8xLCBhZXMoeT1pbmRpdmlkdWFsX20sIHg9aW5kaXZpZHVhbF9jKSkgKyBzdGF0X3N1bW1hcnlfaGV4KGFlcyh6ID0gcmV3YXJkKSwgYmlucz0xMCkgKyBmYWNldF93cmFwKH4gcnVuX251bWJlcikgKyBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gcm91bmQoc2VxKG1pbihydW5fMSRpbmRpdmlkdWFsX2MpLCBtYXgocnVuXzEkaW5kaXZpZHVhbF9jKSwgYnkgPSAyNSksMSkpICsgZ2d0aXRsZSgiSGV4YWdvbmFsIGhlYXRtYXAgb2YgaW5wdXQgcGFyYW1ldGVycyBhbmQgQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvciIpCnAkbGFiZWxzJGZpbGwgPC0gIkFic29sdXRlIFxuUGVyY2VudGFnZSBcbkVycm9yIgpwcmludChwKQoKZ2dzYXZlKCJ+L0Rlc2t0b3AvZ2VuZXRpY19hbGdvcml0aG1fcHJvZ3Jlc3Npb24ucG5nIikKYGBgCgpgYGB7cn0KcnVuXzEgJT4lIGRwbHlyOjpncm91cF9ieShydW5fbnVtYmVyKSAlPiUgZHBseXI6OnN1bW1hcmlzZShhdmdfcmV3YXJkID0gbWVhbihyZXdhcmQpKSAlPiUgZ2dwbG90KCkrIGdlb21fc21vb3RoKGRhdGE9cnVuXzEsIGFlcyh4PXJ1bl9udW1iZXIsIHJld2FyZCkpK2dlb21fbGluZShhZXMoeD1ydW5fbnVtYmVyLCB5PWF2Z19yZXdhcmQpKStnZ3RpdGxlKCJBdmVyYWdlIHJld2FyZCB2cyBQb3B1bGF0aW9uIE51bWJlciIpIApgYGAKCgoKCgpgYGB7cn0KIyBhY2N1cmF0ZV9hcmVhID0gZmlsdGVyKHJ1bl8xLCBpbmRpdmlkdWFsX2M8LTQuOCwgaW5kaXZpZHVhbF9jPi01MCwgaW5kaXZpZHVhbF9tIDwwLjAwMywgaW5kaXZpZHVhbF9tID4gMC4wMDIzKQojIGFjY3VyYXRlX2FyZWEgPSBmaWx0ZXIocnVuXzEsIHJld2FyZCA8IDAuMikKIyBhY2N1cmF0ZV9hcmVhID0gZmlsdGVyKHJ1bl8xLCBydW5fbnVtYmVyPT0xMSkKYWNjdXJhdGVfYXJlYSA9IGZpbHRlcihydW5fMSwgcnVuX251bWJlciA9PSAxNikKCmFjY3VyYXRlX2FyZWEKCmBgYAoKYGBge3J9CiBnZ3Bsb3QoZGF0YT1hY2N1cmF0ZV9hcmVhLCBhZXMoeD1pbmRpdmlkdWFsX2MsIHk9aW5kaXZpZHVhbF9tLCBjb2xvcj1yZXdhcmQpKStnZW9tX2ppdHRlcigpK2dlb21fcG9pbnQoY29sb3I9InJlZCIsIGFscGhhPTAuNSkgKyBnZ3RpdGxlKCJTY2F0dGVyIGFuZCBKaXR0ZXJwbG90IG9mIFJld2FyZCBhZ2FpbnN0IElucHV0IFBhcmFtZXRlcnMgZm9yIFxuTGFzdCBQb3B1bGF0aW9uIG9mIEdBIikKCmBgYAoKCmBgYHtyfQphY2N1cmF0ZV9hcmVhX2xvbmcgPSBnYXRoZXIoYWNjdXJhdGVfYXJlYSwgImtleSIsICJ2YWx1ZSIsICJjb2FsIiwgImNjZ3QiLCAid2luZCIsICJudWNsZWFyIiwgInNvbGFyIikKYWNjdXJhdGVfYXJlYV9sb25nX3BlcmMgPSBhY2N1cmF0ZV9hcmVhX2xvbmcgJT4lIGdyb3VwX2J5KGlkKSAlPiUgbXV0YXRlKHZhbHVlX3BlcmMgPSB2YWx1ZS9zdW0odmFsdWUpKQoKYGBgCgoKYGBge3J9CnBhcmFtc19tb3JlX3RoYW5fMTAgPSBydW5fMSAlPiUgZ3JvdXBfYnkoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pICU+JSBkcGx5cjo6bXV0YXRlKG51bWJlciA9IG4oKSkgJT4lIHVuZ3JvdXAoKSAlPiUgZmlsdGVyKG51bWJlcj4gMTApICU+JSBkcGx5cjo6bXV0YXRlX2lmKGlzLm51bWVyaWMsIHJvdW5kLCA2KSAKCnBhcmFtc19tb3JlX3RoYW5fMTAgJT4lIGdncGxvdChhZXMoYXMuZmFjdG9yKGluZGl2aWR1YWxfYyksIHJld2FyZCkpICtnZW9tX3Zpb2xpbigpK2ZhY2V0X3dyYXAofmluZGl2aWR1YWxfbSkrIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpICtnZW9tX2ppdHRlcihwb3NpdGlvbj1wb3NpdGlvbl9qaXR0ZXIoMC4yKSwgYWxwaGE9MC4wNSkgKyBnZ3RpdGxlKCJWaW9saW4gcGxvdCBvZiBpbnB1dCBwYXJhbWV0ZXJzIHdpdGggbW9yZSB0aGFuIDEwIHNpbXVsYXRpb25zIFxuYWdhaW5zdCBkaXN0cmlidXRpb24gb2YgcmV3YXJkIikKCmBgYAoKYGBge3J9CmFjY3VyYXRlX2FyZWEgJT4lIG11dGF0ZV9pZihpcy5udW1lcmljLCByb3VuZCwgNikgJT4lIGdncGxvdChhZXMoYXMuZmFjdG9yKGluZGl2aWR1YWxfYyksIHJld2FyZCkpICtnZW9tX3Zpb2xpbigpK2ZhY2V0X3dyYXAofmluZGl2aWR1YWxfbSkrIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpICtnZW9tX2ppdHRlcihwb3NpdGlvbj1wb3NpdGlvbl9qaXR0ZXIoMC4yKSkrIGdndGl0bGUoIlZpb2xpbiBwbG90IG9mIGlucHV0IHBhcmFtZXRlcnMgd2l0aCBtb3JlIHRoYW4gMTAgc2ltdWxhdGlvbnMgb2YgbGFzdCBwb3B1bGF0aW9uIG9mIEdBXG5hZ2FpbnN0IGRpc3RyaWJ1dGlvbiBvZiByZXdhcmQiKQoKYGBgCgoKYGBge3J9CmFjdHVhbF9taXggPSByZWFkX2NzdignL1VzZXJzL2IxMDE3NTc5L0RvY3VtZW50cy9QaEQvUHJvamVjdHMvMTAtRUxFQ1NJTS9lbGVjc2ltL2RhdGEvcHJvY2Vzc2VkL2VsZWN0cmljaXR5X21peC9lbmVyZ3lfbWl4X2hpc3RvcmljYWwuY3N2JykKYWN0dWFsX21peF8yMDE4ID0gZmlsdGVyKGFjdHVhbF9taXgsIHllYXI9PTIwMTgpCgoKYWN0dWFsX21peF8yMDE4JHR5cGUgPSAiYWN0dWFsIgoKYWN0dWFsX21peF8yMDE4CmBgYAoKYGBge3J9CmFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkID0gZmlsdGVyKGFjdHVhbF9taXhfMjAxOCwgdmFyaWFibGUgJWluJSBjKCJjY2d0IiwgJ3dpbmQnLCAnbnVjbGVhcicsICdzb2xhcicsICdjb2FsJykpCmFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkID0gYWN0dWFsX21peF8yMDE4X3JlZHVjZWQgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkKYWN0dWFsX21peF8yMDE4X3JlZHVjZWQgPSBkcGx5cjo6cmVuYW1lKGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCBrZXk9dmFyaWFibGUpCmhlYWQoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQpCmBgYApgYGB7cn0KYWNjdXJhdGVfYXJlYV9sb25nX3BlcmMkdHlwZSA9ICdwcmVkaWN0ZWQnCmFjY3VyYXRlX2FyZWFfbG9uZ19wZXJjCgpjb21wYXJpc29uID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYWNjdXJhdGVfYXJlYV9sb25nX3BlcmMpLCAia2V5IiwgInR5cGUiLCAidmFsdWUiLCAndmFsdWVfcGVyYycpLCBzZWxlY3QoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIC1YMSwgLXllYXIpKQpgYGAKCgoKCgpgYGB7cn0KZ2dwbG90KGNvbXBhcmlzb24sIGFlcyh4PWtleSwgeT12YWx1ZV9wZXJjLCBmaWxsPXR5cGUpKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiYmFyIiwgZnVuLnkgPSBtZWFuLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSsgc3RhdF9zdW1tYXJ5KGdlb20gPSAiZXJyb3JiYXIiLCBmdW4uZGF0YSA9IG1lYW5fc2UsIHBvc2l0aW9uID0gImRvZGdlMiIpICsgeWxhYigiRW5lcmd5IE1peCAoJSkiKSArIGdndGl0bGUoIkNvbWJpbmF0aW9uIG9mIGFsbCBydW5zIGluIGxhc3QgZ2VuZXRpYyBhbGdvcml0aG0gcnVuIikKCmdnc2F2ZSgnfi9EZXNrdG9wL2F2ZXJhZ2VfZXJyb3Jfb2ZfYmVzdF9wYXJhbXMucG5nJykKYGBgCgpgYGB7cn0KYmVzdF9yZXN1bHQgPSBydW5fMSAlPiUgZ3JvdXBfYnkoImlkIikgJT4lIGZpbHRlcihyZXdhcmQ9PW1pbihyZXdhcmQpKSAlPiUgZ2F0aGVyKCJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIG11dGF0ZSh0eXBlPSJwcmVkaWN0ZWQiKQoKY29tcGFyaXNvbl9iZXN0ID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYmVzdF9yZXN1bHQpLCAia2V5IiwgInR5cGUiLCAidmFsdWUiLCAndmFsdWVfcGVyYycpLCBzZWxlY3QoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIC1YMSwgLXllYXIpKQoKZ2dwbG90KGNvbXBhcmlzb25fYmVzdCwgYWVzKHg9a2V5LCB5PXZhbHVlX3BlcmMsIGZpbGw9dHlwZSkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UyIikgKyB5bGFiKCJFbmVyZ3kgTWl4ICglKSIpICsgZ2d0aXRsZSgiQmVzdCBzaW5nbGUgcnVuIikKYGBgCgoKYGBge3J9CnBkY19leGFtcGxlID0gcmVhZF9jc3YoJy9Vc2Vycy9iMTAxNzU3OS9Eb2N1bWVudHMvUGhEL1Byb2plY3RzLzEwLUVMRUNTSU0vbm90ZWJvb2tzL3ZhbGlkYXRpb24tb3B0aW1pc2F0aW9uL2RhdGEvcHJpY2VfZGVtYW5kX2N1cnZlX2V4YW1wbGUuY3N2JykKCgpkZW1hbmRfcmFuZ2UgPSBkYXRhLmZyYW1lKHg9c2VxKGZyb209bWluKHBkY19leGFtcGxlJHNlZ21lbnRfZGVtYW5kKSxtYXgocGRjX2V4YW1wbGUkc2VnbWVudF9kZW1hbmQpLGxlbmd0aC5vdXQ9NTAwKSkKCmdldF9saW5lID0gZnVuY3Rpb24oYywgbSl7CiAgICB5ID0gbSAqIGRlbWFuZF9yYW5nZSArIGMKICAgIHJldHVybih5KQp9CgpnZXRfeCA9IGZ1bmN0aW9uKCl7CiAgICByZXR1cm4oZGVtYW5kX3JhbmdlKQp9CgojIGxpbmVzID0gYWNjdXJhdGVfYXJlYSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBhcHBseShnZXRfbGluZSguKSkKCmxpbmVzID0gZGRwbHkoYWNjdXJhdGVfYXJlYSwgLihpZCksIHRyYW5zZm9ybSwgeT1nZXRfbGluZShpbmRpdmlkdWFsX2MsIGluZGl2aWR1YWxfbSksIHg9Z2V0X3goKSkKCgpnZ3Bsb3QoKSArIGdlb21fbGluZShkYXRhPWxpbmVzLCBhZXMoeD14LjEsIHk9eCwgZ3JvdXA9aWQsIGNvbG9yPXJld2FyZCkpICsgc3RhdF9zbW9vdGgoZGF0YT1wZGNfZXhhbXBsZSwgYWVzKHg9c2VnbWVudF9kZW1hbmQsIHk9YWNjZXB0ZWRfcHJpY2UpLCBtZXRob2Q9ImxtIiwgY29sPSJyZWQiKSArIGdlb21fcG9pbnQoZGF0YT1wZGNfZXhhbXBsZSwgYWVzKHg9c2VnbWVudF9kZW1hbmQsIHk9YWNjZXB0ZWRfcHJpY2UpKSArIHhsYWIoIkRlbWFuZCAoTVcpIikgKyB5bGFiKCJBY2NlcHRlZCBQcmljZSIpICsgZ2d0aXRsZSgiTGFzdCBnZW5ldGljIGFsZ29yaXRobSBydW4gcHJpY2UgY3VydmVzIGJldHdlZW4gMjAyMy0yMDI4IFxuY29tcGFyZWQgdG8gcHJpY2UgaW4gMjAxOCIpCgpgYGAKCmBgYHtyfQpiZXN0Xzc1X3BlcmNlbnRpbGUgPSBhY2N1cmF0ZV9hcmVhICU+JSBncm91cF9ieShpbmRpdmlkdWFsX2MsIGluZGl2aWR1YWxfbSkgJT4lIGRwbHlyOjpzdW1tYXJpc2UobnVtYmVyPW4oKSwgcXVhbnRpbGVzID0gbGlzdChlbmZyYW1lKHF1YW50aWxlKHJld2FyZCwgcHJvYnM9YygwLjI1LDAuNSwwLjc1KSkpKSkgJT4lIHVubmVzdCAlPiUgdW5ncm91cCgpICU+JSBmaWx0ZXIobnVtYmVyPjEwKSAlPiUgZ3JvdXBfYnkobmFtZSkgJT4lZmlsdGVyKHJhbmsodmFsdWUsIHRpZXMubWV0aG9kPSJmaXJzdCIpPT0xKQoKcCA9IGRkcGx5KGJlc3RfNzVfcGVyY2VudGlsZSwgLihuYW1lKSwgdHJhbnNmb3JtLCB5PWdldF9saW5lKGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSwgeD1nZXRfeCgpKSAgJT4lIGdncGxvdCgpICsgZ2VvbV9saW5lKGFlcyh4PXguMSwgeT14LCBjb2xvcj1uYW1lKSkrIHN0YXRfc21vb3RoKGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSwgbWV0aG9kPSJsbSIsIGNvbD0ieWVsbG93IikgKyBnZW9tX3BvaW50KGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSkgKyB4bGFiKCJEZW1hbmQgKE1XKSIpICsgeWxhYigiQWNjZXB0ZWQgUHJpY2UiKSArIGdndGl0bGUoIlByaWNlIGN1cnZlIG9mIGJlc3QgcnVucyBhdCBkaWZmZXJlbnQgcGVyY2VudGlsZXMgb2YgbGFzdCBnZW5ldGljIFxuYWxnb3JpdGhtIHBvcHVsYXRpb24iKQpwJGxhYmVscyRmaWxsIDwtICJQZXJjZW50aWxlcyIKcHJpbnQocCkKYGBgCmBgYHtyfQpiZXN0X3BlcmNlbnRpbGVzX2NvbXBhcmlzb24gPSBpbm5lcl9qb2luKGFjY3VyYXRlX2FyZWEsIGJlc3RfNzVfcGVyY2VudGlsZSwgYnk9YygnaW5kaXZpZHVhbF9jJywgJ2luZGl2aWR1YWxfbScpKSAlPiUgZ2F0aGVyKCJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIG11dGF0ZSh0eXBlPW5hbWUpCgpjb21wYXJpc29uX3BlcmNlbnRpbGVzID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYmVzdF9wZXJjZW50aWxlc19jb21wYXJpc29uKSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkKCmNvbXBhcmlzb25fcGVyY2VudGlsZXMgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikgKyBnZ3RpdGxlKCJDb21wYXJpc29uIG9mIGVuZXJneSBtaXggb2YgZmluYWwgZ2VuZXRpYyBhbGdvcml0aG0gcG9wdWxhdGlvbiBcbmF0IGRpZmZlcmVudCBxdWFudGlsZSBsZXZlbHMiKQoKCmBgYApgYGB7cn0KcmJpbmQoZmlsdGVyKHNlbGVjdCh1bmdyb3VwKGJlc3RfcGVyY2VudGlsZXNfY29tcGFyaXNvbiksICJrZXkiLCAidHlwZSIsICJ2YWx1ZSIsICd2YWx1ZV9wZXJjJyksdHlwZT09IjUwJSIpLCBzZWxlY3QoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIC1YMSwgLXllYXIpKSAlPiUgZ2dwbG90KGFlcyh4PWtleSwgeT12YWx1ZV9wZXJjLCBmaWxsPXR5cGUpKSArIHN0YXRfc3VtbWFyeShnZW9tID0gImJhciIsIGZ1bi55ID0gbWVhbiwgcG9zaXRpb24gPSAiZG9kZ2UyIikrIHN0YXRfc3VtbWFyeShnZW9tID0gImVycm9yYmFyIiwgZnVuLmRhdGEgPSBtZWFuX3NlLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSArIGdndGl0bGUoIkJlc3QgcGFyYW1ldGVyIGNvbWJpbmF0aW9ucyBmcm9tIGZpbmFsIGdlbmV0aWMgYWxnb3JpdGhtIHJ1biIpCgpgYGAKCgojIyBCRVNUIFBBUkFNRVRFUiBDT01CSU5BVElPTiBGUk9NIEFMTCBSVU5TCgpgYGB7cn0KYmVzdF83NV9wZXJjZW50aWxlX2FsbF9ydW5zID0gcnVuXzEgJT4lIGdyb3VwX2J5KGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSAlPiUgZHBseXI6OnN1bW1hcmlzZShudW1iZXI9bigpLCBxdWFudGlsZXMgPSBsaXN0KGVuZnJhbWUocXVhbnRpbGUocmV3YXJkLCBwcm9icz1jKDAuMjUsMC41LDAuNzUsIDAuOSwgMS4wKSkpKSkgJT4lIHVubmVzdCAlPiUgdW5ncm91cCgpICU+JSBmaWx0ZXIobnVtYmVyPjEwKSAlPiUgZ3JvdXBfYnkobmFtZSkgJT4lZmlsdGVyKHJhbmsodmFsdWUsIHRpZXMubWV0aG9kPSJmaXJzdCIpPT0xKQoKcCA9IGRkcGx5KGJlc3RfNzVfcGVyY2VudGlsZV9hbGxfcnVucywgLihuYW1lKSwgdHJhbnNmb3JtLCB5PWdldF9saW5lKGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSwgeD1nZXRfeCgpKSAgJT4lIGdncGxvdCgpICsgZ2VvbV9saW5lKGFlcyh4PXguMSwgeT14LCBjb2xvcj1uYW1lKSkrIHN0YXRfc21vb3RoKGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSwgbWV0aG9kPSJsbSIsIGNvbD0ieWVsbG93IikgKyBnZW9tX3BvaW50KGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSkgKyB4bGFiKCJEZW1hbmQgKE1XKSIpICsgeWxhYigiQWNjZXB0ZWQgUHJpY2UiKSArIGdndGl0bGUoIlBlcmNlbnRpbGVzIGZyb20gYWxsIHJ1bnMiKQpwJGxhYmVscyRmaWxsIDwtICJQZXJjZW50aWxlcyIKcHJpbnQocCkKYGBgCmBgYHtyfQpiZXN0X3BlcmNlbnRpbGVzX2NvbXBhcmlzb24gPSBpbm5lcl9qb2luKHJ1bl8xLCBiZXN0Xzc1X3BlcmNlbnRpbGVfYWxsX3J1bnMsIGJ5PWMoJ2luZGl2aWR1YWxfYycsICdpbmRpdmlkdWFsX20nKSkgJT4lIGdhdGhlcigia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBtdXRhdGUodmFsdWVfcGVyYyA9IHZhbHVlL3N1bSh2YWx1ZSkpICU+JSBtdXRhdGUodHlwZT1uYW1lKQoKY29tcGFyaXNvbl9wZXJjZW50aWxlcyA9IHJiaW5kKHNlbGVjdCh1bmdyb3VwKGJlc3RfcGVyY2VudGlsZXNfY29tcGFyaXNvbiksICJrZXkiLCAidHlwZSIsICJ2YWx1ZSIsICd2YWx1ZV9wZXJjJyksIHNlbGVjdChhY3R1YWxfbWl4XzIwMThfcmVkdWNlZCwgLVgxLCAteWVhcikpCgpjb21wYXJpc29uX3BlcmNlbnRpbGVzICU+JSBnZ3Bsb3QoYWVzKHg9a2V5LCB5PXZhbHVlX3BlcmMsIGZpbGw9dHlwZSkpICsgc3RhdF9zdW1tYXJ5KGdlb20gPSAiYmFyIiwgZnVuLnkgPSBtZWFuLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSsgc3RhdF9zdW1tYXJ5KGdlb20gPSAiZXJyb3JiYXIiLCBmdW4uZGF0YSA9IG1lYW5fc2UsIHBvc2l0aW9uID0gImRvZGdlMiIpCmBgYApgYGB7cn0KcmJpbmQoZmlsdGVyKHNlbGVjdCh1bmdyb3VwKGJlc3RfcGVyY2VudGlsZXNfY29tcGFyaXNvbiksICJrZXkiLCAidHlwZSIsICJ2YWx1ZSIsICd2YWx1ZV9wZXJjJyksdHlwZT09Ijc1JSIgfCB0eXBlPT0iOTAlIiksIHNlbGVjdChhY3R1YWxfbWl4XzIwMThfcmVkdWNlZCwgLVgxLCAteWVhcikpICU+JSBnZ3Bsb3QoYWVzKHg9a2V5LCB5PXZhbHVlX3BlcmMsIGZpbGw9dHlwZSkpICsgc3RhdF9zdW1tYXJ5KGdlb20gPSAiYmFyIiwgZnVuLnkgPSBtZWFuLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSsgc3RhdF9zdW1tYXJ5KGdlb20gPSAiZXJyb3JiYXIiLCBmdW4uZGF0YSA9IG1lYW5fc2UsIHBvc2l0aW9uID0gImRvZGdlMiIpICsgZ2d0aXRsZSgiQmVzdCBwYXJhbWV0ZXIgY29tYmluYXRpb25zIGZyb20gYWxsIGdlbmV0aWMgYWxnb3JpdGhtIHJ1bnMiKQoKYGBgCgojIyBDYWxjdWxhdGlvbiBvZiBiZXN0IHF1YW50aWxlIHRvIHNlbGVjdCBjb21iaW5hdGlvbiBvZiBwYXJhbWV0ZXJzIGZvciBsb3dlc3QgcmV3YXJkCgpgYGB7cn0KYmVzdF9wYXJhbXMgPSBwYXJhbXNfbW9yZV90aGFuXzEwICU+JSBncm91cF9ieShpbmRpdmlkdWFsX2MsIGluZGl2aWR1YWxfbSkgJT4lIGdhdGhlcigia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBtdXRhdGUodmFsdWVfcGVyYyA9IHZhbHVlL3N1bSh2YWx1ZSkpICU+JSBncm91cF9ieShpZCwga2V5KSAlPiUgaW5uZXJfam9pbihhY3R1YWxfbWl4XzIwMThfcmVkdWNlZCwgYnk9ImtleSIpICU+JSBtdXRhdGUoZGlmZl9wZXJjID0gYWJzKHZhbHVlX3BlcmMueC12YWx1ZV9wZXJjLnkpKSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBzdW1tYXJpc2UobWVhbl9kaWZmX3BlcmMgPSBtZWFuKGRpZmZfcGVyYyksIGluZGl2aWR1YWxfYyA9IG1lYW4oaW5kaXZpZHVhbF9jKSwgaW5kaXZpZHVhbF9tID0gbWVhbihpbmRpdmlkdWFsX20pKSAlPiUgZmlsdGVyKHJhbmsobWVhbl9kaWZmX3BlcmMsIHRpZXMubWV0aG9kPSJmaXJzdCIpPT0xKQoKYmVzdF9wYXJhbXNfY29tcGFyaXNvbiA9IGZpbHRlcihydW5fMSxkcGx5cjo6bmVhcih4PWluZGl2aWR1YWxfYywgeT1iZXN0X3BhcmFtcyRpbmRpdmlkdWFsX2NbMV0sIHRvbD0wLjAwMDAxKSxkcGx5cjo6bmVhcih4PWluZGl2aWR1YWxfbSwgeT1iZXN0X3BhcmFtcyRpbmRpdmlkdWFsX21bMV0sIHRvbD0wLjAwMDAxKSkgJT4lIGdhdGhlcigia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgZHBseXI6Omdyb3VwX2J5KGlkKSAlPiUgZHBseXI6Om11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIGRwbHlyOjptdXRhdGUodHlwZT0icHJlZGljdGVkIikKCmNvbXBhcmlzb25fYmVzdF9wYXJhbXMgPSByYmluZChzZWxlY3QodW5ncm91cChiZXN0X3BhcmFtc19jb21wYXJpc29uKSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkKCmNvbXBhcmlzb25fYmVzdF9wYXJhbXMgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikgKyBnZ3RpdGxlKCJDb21wYXJpc29uIG9mIHByZWRpY3RlZCB2cyBhY3R1YWwgZm9yIGJlc3QgcGFyYW1ldGVyIHNldCBvdmVyIDEwIGZyb20gYWxsIHJ1bnMiKQoKCgojIyBHZXR0aW5nIE1BUEUgZm9yIGJlc3QgcGFyYW0KCnByZWRpY3RlZF9tZWFuc19iZXN0X3BhcmFtID0gZHBseXI6OmZpbHRlcihydW5fMSxkcGx5cjo6bmVhcih4PWluZGl2aWR1YWxfYywgeT1iZXN0X3BhcmFtcyRpbmRpdmlkdWFsX2NbMV0sIHRvbD0wLjEpLGRwbHlyOjpuZWFyKHg9aW5kaXZpZHVhbF9tLCB5PWJlc3RfcGFyYW1zJGluZGl2aWR1YWxfbVsxXSwgdG9sPTAuMSkpICU+JSBnYXRoZXIoImtleSIsICJ2YWx1ZSIsICJjb2FsIiwgImNjZ3QiLCAid2luZCIsICJudWNsZWFyIiwgInNvbGFyIikgJT4lIGRwbHlyOjpncm91cF9ieShpZCkgJT4lIGRwbHlyOjptdXRhdGUodmFsdWVfcGVyYyA9IHZhbHVlL3N1bSh2YWx1ZSkpICU+JSBkcGx5cjo6bXV0YXRlKHR5cGU9InByZWRpY3RlZCIpICU+JSBkcGx5cjo6Z3JvdXBfYnkoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20sIGtleSkgJT4lIGRwbHlyOjpzdW1tYXJpc2UocHJlZGljdGVkX3BlcmMgPSBtZWFuKHZhbHVlX3BlcmMpKSAKCnByaW50KHBhc3RlKCJNQVBFID0gIiwgTUFQRShwcmVkaWN0ZWRfbWVhbnNfYmVzdF9wYXJhbSRwcmVkaWN0ZWRfcGVyYywgYWN0dWFsX21peF8yMDE4X3JlZHVjZWQkdmFsdWVfcGVyYykpKQoKYGBgCgpgYGB7cn0KcCA9IGJlc3RfcGFyYW1zICU+JSB0cmFuc2Zvcm0oeT1nZXRfbGluZShpbmRpdmlkdWFsX2MsIGluZGl2aWR1YWxfbSksIHg9Z2V0X3goKSkgICU+JSBnZ3Bsb3QoKSArIGdlb21fbGluZShhZXMoeD14LjEsIHk9eCkpKyBzdGF0X3Ntb290aChkYXRhPXBkY19leGFtcGxlLCBhZXMoeD1zZWdtZW50X2RlbWFuZCwgeT1hY2NlcHRlZF9wcmljZSksIG1ldGhvZD0ibG0iLCBjb2w9InllbGxvdyIpICsgZ2VvbV9wb2ludChkYXRhPXBkY19leGFtcGxlLCBhZXMoeD1zZWdtZW50X2RlbWFuZCwgeT1hY2NlcHRlZF9wcmljZSkpICsgeGxhYigiRGVtYW5kIChNVykiKSArIHlsYWIoIkFjY2VwdGVkIFByaWNlIikgKyBnZ3RpdGxlKCJCZXN0IHBhcmFtYXRlciBjb21iaW5hdGlvbiBwcmljZSBjdXJ2ZSIpCnAkbGFiZWxzJGZpbGwgPC0gIlBlcmNlbnRpbGVzIgpwcmludChwKQoKYGBgCgoKIyBDYWxjdWxhdGlvbiBvZiBiZXN0IHBhcmFtZXRlcnMgd2l0aCBsb3dlc3QgdmFyaWFuY2UgYW5kIGVycm9yCmBgYHtyfQojIGJlc3RfcGFyYW1zID0gcGFyYW1zX21vcmVfdGhhbl8xMCAlPiUgZ3JvdXBfYnkoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pICU+JSBnYXRoZXIoImtleSIsICJ2YWx1ZSIsICJjb2FsIiwgImNjZ3QiLCAid2luZCIsICJudWNsZWFyIiwgInNvbGFyIikgJT4lIGdyb3VwX2J5KGlkKSAlPiUgbXV0YXRlKHZhbHVlX3BlcmMgPSB2YWx1ZS9zdW0odmFsdWUpKSAlPiUgZ3JvdXBfYnkoaWQsIGtleSkgJT4lIGlubmVyX2pvaW4oYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIGJ5PSJrZXkiKSAlPiUgbXV0YXRlKGRpZmZfcGVyYyA9IGFicyh2YWx1ZV9wZXJjLngtdmFsdWVfcGVyYy55KSkgJT4lIGdyb3VwX2J5KGlkKSAlPiUgc3VtbWFyaXNlKG1lYW5fZGlmZl9wZXJjID0gbWVhbihkaWZmX3BlcmMpLCBpbmRpdmlkdWFsX2MgPSBtZWFuKGluZGl2aWR1YWxfYyksIGluZGl2aWR1YWxfbSA9IG1lYW4oaW5kaXZpZHVhbF9tKSkgJT4lIGZpbHRlcihyYW5rKG1lYW5fZGlmZl9wZXJjLCB0aWVzLm1ldGhvZD0iZmlyc3QiKT09MSkKCiMgY2FsY3VsYXRlX21lYW5fZXJyb3IgPSBmdW5jdGlvbihkZil7CiMgICAgIAojICAgICByZXR1cm4oZ2F0aGVyKGRmLCAia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgaW5uZXJfam9pbihhY3R1YWxfbWl4XzIwMThfcmVkdWNlZCwgYnk9J2tleScpKQojIH0KCiMgcGFyYW1zX21vcmVfdGhhbl8xMCAlPiUgZ3JvdXBfYnkoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pICU+JSBkbyguLCBjYWxjdWxhdGVfbWVhbl9lcnJvcikKIyBkZHBseShwYXJhbXNfbW9yZV90aGFuXzEwLCAuKGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSwgY2FsY3VsYXRlX21lYW5fZXJyb3IpCgpwYXJhbXNfc2RfbWVhbl9yZXdhcmQgPSBwYXJhbXNfbW9yZV90aGFuXzEwICU+JSBkcGx5cjo6Z3JvdXBfYnkoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pICU+JSBnYXRoZXIoImtleSIsICJ2YWx1ZSIsICJjb2FsIiwgImNjZ3QiLCAid2luZCIsICJudWNsZWFyIiwgInNvbGFyIikgJT4lIGRwbHlyOjpncm91cF9ieShpZCkgJT4lIGRwbHlyOjptdXRhdGUodmFsdWVfcGVyYyA9IHZhbHVlL3N1bSh2YWx1ZSkpICU+JSBkcGx5cjo6Z3JvdXBfYnkoaWQsIGtleSkgJT4lIGRwbHlyOjppbm5lcl9qb2luKGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCBieT0ia2V5IikgJT4lIGRwbHlyOjptdXRhdGUoZGlmZl9wZXJjID0gYWJzKHZhbHVlX3BlcmMueC12YWx1ZV9wZXJjLnkpKSAlPiUgZHBseXI6Omdyb3VwX2J5KGlkKSAlPiUgZHBseXI6OnN1bW1hcmlzZShkaWZmX3NkID0gc2QoZGlmZl9wZXJjKSwgbWVhbl9kaWZmX3BlcmMgPSBtZWFuKGRpZmZfcGVyYyksIGluZGl2aWR1YWxfYyA9IG1lYW4oaW5kaXZpZHVhbF9jKSwgaW5kaXZpZHVhbF9tID0gbWVhbihpbmRpdmlkdWFsX20pKSAlPiUgZHBseXI6Omdyb3VwX2J5KGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSAlPiUgZHBseXI6OnN1bW1hcmlzZShkaWZmX3NkID0gc2QoZGlmZl9zZCksIG1lYW5fZGlmZl9wZXJjID0gZHBseXI6Om1lYW4obWVhbl9kaWZmX3BlcmMpKQoKcGFyYW1zX3NkX21lYW5fcmV3YXJkX29yZGVyZWQgPSBhcnJhbmdlKHBhcmFtc19zZF9tZWFuX3Jld2FyZCwgZGlmZl9zZCwgbWVhbl9kaWZmX3BlcmMpCmZyb250aWVyID0gZ2V0X2Zyb250aWVyKGFzLmRhdGEuZnJhbWUocGFyYW1zX3NkX21lYW5fcmV3YXJkX29yZGVyZWQpLCBkaWZmX3NkLCBtZWFuX2RpZmZfcGVyYywgZGVjcmVhc2luZyA9IEZBTFNFLCBxdWFkcmFudCA9ICJib3R0b20ubGVmdCIpCgpwYXJhbXNfc2RfbWVhbl9yZXdhcmQgJT4lIGdncGxvdCgpICsgZ2VvbV9wb2ludChhZXMoZGlmZl9zZCwgbWVhbl9kaWZmX3BlcmMpKSArIGdlb21fbGluZShkYXRhPWZyb250aWVyLCBhZXMoZGlmZl9zZCwgbWVhbl9kaWZmX3BlcmMpLCBjb2xvcj0icmVkIikgKyBnZ3RpdGxlKCJQYXJldG8gZnJvbnRpZXIgb2Ygc3RhbmRhcmQgZGV2aWF0aW9uIHZlcnN1cyBtZWFuIGZvciBzaW11bGF0aW9ucyBcbm9mIG1vcmUgdGhhbiAxMCBydW5zIikKCmBgYAoKYGBge3J9CnBhcmFtc19tb3JlX3RoYW5fMTBfcGVyYyA9IHBhcmFtc19tb3JlX3RoYW5fMTAgJT4lIGRwbHlyOjpncm91cF9ieShpbmRpdmlkdWFsX2MsIGluZGl2aWR1YWxfbSkgJT4lIGdhdGhlcigia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBkcGx5cjo6bXV0YXRlKHZhbHVlX3BlcmMgPSB2YWx1ZS9zdW0odmFsdWUpKQoKZnJvbnRpZXJfZnVsbF9kYXRhID0gZnJvbnRpZXIgJT4lIGRwbHlyOjppbm5lcl9qb2luKHBhcmFtc19zZF9tZWFuX3Jld2FyZF9vcmRlcmVkLCBieT1jKCdkaWZmX3NkJywgJ21lYW5fZGlmZl9wZXJjJykpICU+JWRwbHlyOjogaW5uZXJfam9pbihwYXJhbXNfbW9yZV90aGFuXzEwX3BlcmMsIGJ5PWMoImluZGl2aWR1YWxfYyIsICJpbmRpdmlkdWFsX20iKSkgJT4lIGRwbHlyOjptdXRhdGUodHlwZT1wYXN0ZShtZWFuX2RpZmZfcGVyYyxkaWZmX3NkKSkKCgpyYmluZChmaWx0ZXIoc2VsZWN0KGZyb250aWVyX2Z1bGxfZGF0YSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSksIHNlbGVjdChhY3R1YWxfbWl4XzIwMThfcmVkdWNlZCwgLVgxLCAteWVhcikpICU+JSBnZ3Bsb3QoYWVzKHg9a2V5LCB5PXZhbHVlX3BlcmMsIGZpbGw9dHlwZSkpICsgc3RhdF9zdW1tYXJ5KGdlb20gPSAiYmFyIiwgZnVuLnkgPSBtZWFuLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSsgc3RhdF9zdW1tYXJ5KGdlb20gPSAiZXJyb3JiYXIiLCBmdW4uZGF0YSA9IG1lYW5fc2UsIHBvc2l0aW9uID0gImRvZGdlMiIpICsgZ2d0aXRsZSgiQmVzdCBwYXJhbWV0ZXIgY29tYmluYXRpb25zIGZyb20gYWxsIGdlbmV0aWMgYWxnb3JpdGhtIHJ1bnMiKQogICAgCmBgYAoKYGBge3J9CmJlc3Rfb25lID0gZHBseXI6OnNlbGVjdChkcGx5cjo6ZmlsdGVyKGZyb250aWVyX2Z1bGxfZGF0YSwgbWVhbl9kaWZmX3BlcmM8MC4wNjcpLCAia2V5IiwgInR5cGUiLCAidmFsdWUiLCAndmFsdWVfcGVyYycpCmJlc3Rfb25lJHR5cGUgPSAiU2ltdWxhdGVkIgoKYWN0dWFsX21peF8yMDE4X3JlZHVjZWQkdHlwZSA9ICJBY3R1YWwiCgpyYmluZChiZXN0X29uZSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikgKyB0aGVtZV9jbGFzc2ljKCkgK3RoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xOCkpKyB4bGFiKCIiKSArIHlsYWIoIkVsZWN0cmljaXR5IE1peCAoJSkiKSArc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICdTZXQyJykKCmdnc2F2ZSgifi9Eb2N1bWVudHMvUGhEL1Byb2plY3RzLzEwLUVMRUNTSU0vbm90ZWJvb2tzL3ZhbGlkYXRpb24tb3B0aW1pc2F0aW9uL2ZpZ3VyZXMvaW50cm9kdWN0aW9uL2Jlc3RfcnVuLnBkZiIpCmBgYAoKYGBge3J9CmJlc3RfYWN0dWFsID0gcmJpbmQoYmVzdF9vbmUsIHNlbGVjdChhY3R1YWxfbWl4XzIwMThfcmVkdWNlZCwgLVgxLCAteWVhcikpCmJlc3RfYWN0dWFsX3N0YXRzID0gYmVzdF9hY3R1YWwgJT4lIGRwbHlyOjpncm91cF9ieSh0eXBlLCBrZXkpICU+JSBkcGx5cjo6c3VtbWFyaXNlKHNkX3ZhbHVlID0gc2QodmFsdWVfcGVyYyksIG1lYW5fdmFsdWUgPSBtZWFuKHZhbHVlX3BlcmMpKSAKCmFjdHVhbHMgPSBkcGx5cjo6ZmlsdGVyKGJlc3RfYWN0dWFsX3N0YXRzLCB0eXBlPT0iQWN0dWFsIikkbWVhbl92YWx1ZQpwcmVkaWN0ZWQgPSBkcGx5cjo6ZmlsdGVyKGJlc3RfYWN0dWFsX3N0YXRzLCB0eXBlPT0iU2ltdWxhdGVkIikkbWVhbl92YWx1ZQoKUk1TRShhY3R1YWxzLHByZWRpY3RlZCkKYGBgCgpgYGB7cn0KcGRjX2V4YW1wbGUkc2VnbWVudF9kZW1hbmQgPSBwZGNfZXhhbXBsZSRzZWdtZW50X2RlbWFuZC8xMDAwCgoKYmVzdF9jID0gZmlsdGVyKGZyb250aWVyX2Z1bGxfZGF0YSwgbWVhbl9kaWZmX3BlcmM8MC4wNjcpJGluZGl2aWR1YWxfY1sxXQpiZXN0X20gPSBmaWx0ZXIoZnJvbnRpZXJfZnVsbF9kYXRhLCBtZWFuX2RpZmZfcGVyYzwwLjA2NykkaW5kaXZpZHVhbF9tWzFdCgpiZXN0X3BhcmFtcyAlPiUgdHJhbnNmb3JtKHk9Z2V0X2xpbmUoYmVzdF9jLCBiZXN0X20pLCB4PWdldF94KCkvMTAwMCkgICU+JSBnZ3Bsb3QoKSArIHN0YXRfc21vb3RoKGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlLCBjb2xvcj0iU2ltdWxhdGVkIEZpdCAoMjAxOCkiKSwgbWV0aG9kPSJsbSIpICsgZ2VvbV9wb2ludChkYXRhPXBkY19leGFtcGxlLCBhZXMoeD1zZWdtZW50X2RlbWFuZCwgeT1hY2NlcHRlZF9wcmljZSwgY29sb3I9IlBEQyAoMjAxOCkiKSkgKyBnZW9tX2xpbmUoYWVzKHg9eC4xLCB5PXgsIGNvbG9yPSJQUERDIiksIHNpemU9MikgKyB4bGFiKCJEZW1hbmQgKEdXKSIpICsgeWxhYigiQWNjZXB0ZWQgUHJpY2UgKMKjL01XaCkiKSArIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lID0gIkRhdGEiLCAgdmFsdWVzID0gYygiUERDICgyMDE4KSIgPSAib3JhbmdlIiwgIlBQREMiID0gIiMzMDY5OTYiLCAnU2ltdWxhdGVkIEZpdCAoMjAxOCknPSdyZWQnKSkgICsgdGhlbWVfY2xhc3NpYygpICt0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTgpKSsgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSwgcGxvdC5tYXJnaW49Z3JpZDo6dW5pdChjKDAsMCwwLDApLCAibW0iKSkgCgoKCgpnZ3NhdmUoIn4vRG9jdW1lbnRzL1BoRC9Qcm9qZWN0cy8xMC1FTEVDU0lNL25vdGVib29rcy92YWxpZGF0aW9uLW9wdGltaXNhdGlvbi9maWd1cmVzL3Jlc3VsdHMvYmVzdF9ydW5fcHJpY2VfZHVyX2N1cnZlLnBkZiIsIGRwaT0xMDAwKQpgYGAKCmBgYHtyfQpwPWdncGxvdChydW5fMSwgYWVzKHk9aW5kaXZpZHVhbF9tLCB4PWluZGl2aWR1YWxfYykpICsgc3RhdF9zdW1tYXJ5X2hleChhZXMoeiA9IHRpbWVfdGFrZW4pLCBiaW5zPTEwKSArIGZhY2V0X3dyYXAofiBydW5fbnVtYmVyKSArIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSByb3VuZChzZXEobWluKHJ1bl8xJGluZGl2aWR1YWxfYyksIG1heChydW5fMSRpbmRpdmlkdWFsX2MpLCBieSA9IDI1KSwxKSkgKyBnZ3RpdGxlKCJIZXhhZ29uYWwgaGVhdG1hcCBvZiBpbnB1dCBwYXJhbWV0ZXJzIGFnYWluc3QgdGltZSB0YWtlbiB3aXRoIHJlc3BlY3QgdG8gcG9wdWxhdGlvbiBudW1iZXIiKQpwJGxhYmVscyRmaWxsIDwtICJUaW1lIFxuIFRha2VuIgpwcmludChwKQoKZ2dzYXZlKCd+L0Rlc2t0b3AvdGltZS10YWtlbi1wYXJhbWV0ZXJzLnBuZycpCmBgYAoKYGBge3J9CnJ1bl8xICU+JSBhcnJhbmdlKGRlc2MocnVuX251bWJlcikpICU+JSBnZ3Bsb3QoYWxwaGE9MC4xLCBhZXMoeT1pbmRpdmlkdWFsX20sIHg9aW5kaXZpZHVhbF9jKSkgKyBnZW9tX3BvaW50KGFlcyhjb2xvcj1ydW5fbnVtYmVyLCBzaXplPXRpbWVfdGFrZW4pKSArIGdndGl0bGUoIlNjYXR0ZXIgcGxvdCBzaG93aW5nIHJ1biBudW1iZXIgYWdhaW5zdCB0aW1lIHRha2VuIGFuZCByZXdhcmQiKQpgYGAKCmBgYHtyfQpydW5fMSAlPiUgZmlsdGVyKGluZGl2aWR1YWxfYzwtMywgaW5kaXZpZHVhbF9tIDwwLjAwMjUsIGluZGl2aWR1YWxfbSA+IDAuMDAxNSkgJT4lIGdncGxvdChhZXMoeD1hcy5mYWN0b3IocnVuX251bWJlciksIHk9dGltZV90YWtlbikpICsgZ2VvbV92aW9saW4oKSArIGdndGl0bGUoIlZpb2xpbiBwbG90IG9mIHRpbWUgdGFrZW4gZm9yIGVhY2ggc2ltdWxhdGlvbiBydW4gYWdhaW5zdCBwb3B1bGF0aW9uIG51bWJlciIpCmBgYApgYGB7cn0KZ2dwbG90KGRhdGE9cnVuXzEsIGFlcyh4PWFzLmZhY3RvcihydW5fbnVtYmVyKSwgeT1yZXdhcmQpKStnZW9tX2JveHBsb3QoKStnZW9tX2ppdHRlcihwb3NpdGlvbj1wb3NpdGlvbl9qaXR0ZXIoMC4yKSwgYWxwaGE9MC41LCBjb2xvcj0nYmx1ZScpICsgZ2d0aXRsZSgiQm94cGxvdCBhbmQgaml0dGVyIG9mIHJ1biBudW1iZXIgYWdhaW5zdCByZXdhcmQiKQpgYGAKCgpgYGB7cn0KcnVuXzEgJT4lIGZpbHRlcihpbmRpdmlkdWFsX2M8LTMsIGluZGl2aWR1YWxfbSA8MC4wMDI1LCBpbmRpdmlkdWFsX20gPiAwLjAwMTUpICU+JSBnZ3Bsb3QoYWVzKHg9YXMuZmFjdG9yKHJ1bl9udW1iZXIpLCB5PXJld2FyZCkpICsgZ2VvbV92aW9saW4oKSArIGdndGl0bGUoIlZpb2xpbiBwbG90IG9mIHJld2FyZCBhZ2FpbnN0IHBvcHVsYXRpb24gbnVtYmVyIikKCmBgYAoKYGBge3J9CmdncGxvdChydW5fMSwgYWVzKHg9cmV3YXJkLCB5PXRpbWVfdGFrZW4pKStzdGF0X3Ntb290aChtZXRob2QgPSAibG0iKStnZW9tX3BvaW50KCkreGxhYigiQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvciIpICsgZ2d0aXRsZSgiU2NhdHRlciBwbG90IG9mIHRpbWUgdGFrZW4gZm9yIGVhY2ggc2ltdWxhdGlvbiBydW4gYWdhaW5zdCBcbmFic29sdXRlIHBlcmNlbnRhZ2UgZXJyb3IiKQoKYGBgCgoKCmBgYHtyfQpnZ3Bsb3QoKStnZW9tX3BvaW50KGRhdGE9cnVuXzEsIGFlcyh4PXRpbWVfdGFrZW4sIHk9cmV3YXJkLCBjb2xvcj1ydW5fbnVtYmVyKSkgKyBnZ3RpdGxlKCJTY2F0dGVyIHBsb3Qgb2YgdGltZSB0YWtlbiBhZ2FpbnN0IHJld2FyZCIpCmBgYAoKYGBge3J9CnJ1bl8xCgpydW5fMV9sb25nID0gZ2F0aGVyKHJ1bl8xLCAia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKQoKIyBydW5fMV9sb25nICU+JSBpbm5lcl9qb2luKGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCBieT0na2V5JykgJT4lIGdyb3VwX2J5KGlkLGtleSkgJT4lIG11dGF0ZSh0b3RhbF9kaWZmZXJlbmNlID0gdmFsdWUueC12YWx1ZS55KSAlPiUgZ3JvdXBfYnkoaWQsIGtleSkgJT4lIHN1bW1hcmlzZShkaWZmZXJlbmNlX3N1bSA9IHN1bSh0b3RhbF9kaWZmZXJlbmNlKSkKCmdncGxvdChkYXRhPWRpZl9zdW0sIGFlcyh4PXRvdF9kaWZmLCB5PXRpbWVfdGFrZW4sIGNvbG9yPXJld2FyZCkpICsgZ2VvbV9wb2ludCgpK2dlb21fc21vb3RoKCkreGxpbSgtMTEwMDAsMTAwMDApCgojIGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkCmBgYAoKYGBge3J9CmRpZl9zdW0gPSBydW5fMV9sb25nICU+JSBpbm5lcl9qb2luKGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCBieT0na2V5JykgJT4lIGdyb3VwX2J5KGlkLGtleSkgJT4lIG11dGF0ZSh0b3RhbF9kaWZmZXJlbmNlID0gdmFsdWUueC12YWx1ZS55KSAlPiUgZGRwbHkoLihpZCwga2V5KSwgc3VtbWFyaXNlLCBkaWZmZXJlbmNlX3N1bSA9IHN1bSh0b3RhbF9kaWZmZXJlbmNlKSwgdGltZV90YWtlbj10aW1lX3Rha2VuLCByZXdhcmQ9cmV3YXJkLCBydW5fbnVtYmVyPXJ1bl9udW1iZXIpICU+JSBncm91cF9ieShpZCkgJT4lIHN1bW1hcmlzZSh0b3RfZGlmZiA9IHN1bShkaWZmZXJlbmNlX3N1bSksIHRpbWVfdGFrZW49bWVhbih0aW1lX3Rha2VuKSwgcmV3YXJkPW1lYW4ocmV3YXJkKSwgcnVuX251bWJlcj1tZWFuKHJ1bl9udW1iZXIpKQoKcGxvdF9seShkYXRhPWRpZl9zdW0sIHg9fnRvdF9kaWZmLCB5PX50aW1lX3Rha2VuLCB6PX5yZXdhcmQsIHR5cGU9InNjYXR0ZXIzZCIsIG1vZGU9Im1hcmtlcnMiLCBjb2xvcj1+cnVuX251bWJlcikgCmBgYAoKYGBge3J9CmdncGxvdChkYXRhPWRpZl9zdW0sIGFlcyh4PXRvdF9kaWZmLCBjb2xvcj10aW1lX3Rha2VuLCB5PXJld2FyZCkpICsgZ2VvbV9wb2ludCgpK2dlb21fc21vb3RoKCkreGxpbSgtMTEwMDAsMTAwMDApCmBgYAoKYGBge3J9CmdncGxvdChydW5fMSwgYWVzKHg9cnVuX251bWJlciwgeT10aW1lX3Rha2VuLCBjb2xvcj1yZXdhcmQpKSArIGdlb21fcG9pbnQoKSArCiAgIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nKQpgYGA=